package com.config;

import com.annotation.GrpcService;
import io.grpc.BindableService;
import io.grpc.Server;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyServerBuilder;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContextBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.stream.Stream;

/**
 * @author 何永豪
 * @className GrpcServer
 * @description TODO
 * @date 2023/11/7 15:56
 */
@Slf4j
public class GrpcServer implements CommandLineRunner, DisposableBean {

    private Server server;

    @Resource
    private AbstractApplicationContext applicationContext;

    @Resource
    private GrpcProperties grpcProperties;

    @Override
    public void destroy() {
        stop();
    }

    @Override
    public void run(String... args) throws Exception {
        start();
    }

    private SslContextBuilder getSslContextBuilder(){
        SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(new File(grpcProperties.getServerCertPath()), new File(grpcProperties.getServerPrivateKeyPath()));
        sslContextBuilder.trustManager(new File(grpcProperties.getServerTrustCertPath()));
        sslContextBuilder.clientAuth(ClientAuth.REQUIRE);
        return GrpcSslContexts.configure(sslContextBuilder);
    }


    private void start() throws IOException {
        NettyServerBuilder nettyServerBuilder = NettyServerBuilder
                .forPort(grpcProperties.getPort());
//                .sslContext(getSslContextBuilder().build()
//                        );
        scanBeanWithAnnotation(GrpcService.class, BindableService.class)
                .forEach(e->{
                    BindableService bindableService = applicationContext.getBeanFactory().getBean(e, BindableService.class);
                    nettyServerBuilder.addService(bindableService.bindService());
                });
        server = nettyServerBuilder.build().start();
        log.info("grpc start listen {}",grpcProperties.getPort());
        Thread thread = new Thread(() -> {
            try {
                GrpcServer.this.blockUntilShutdown();
            } catch (InterruptedException e) {
                log.error("grpc server stopped");
                throw new RuntimeException(e);
            }
        });
        thread.setDaemon(false);
        thread.start();
    }

    private void stop(){
        if (server !=null){
            server.shutdown();
        }
    }

    private void blockUntilShutdown() throws InterruptedException {
        if (server !=null ){
            server.awaitTermination();
        }
    }

    private <T> Stream<String> scanBeanWithAnnotation(Class<? extends Annotation> annotionType,Class<T> beanType){
        String[] beanNamesForType = applicationContext.getBeanNamesForType(beanType);
        return Stream.of(beanNamesForType).filter(e->{
            BeanDefinition beanDefinition = applicationContext.getBeanFactory().getBeanDefinition(e);
            Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(annotionType);
            if (beansWithAnnotation.containsKey(e)){
                return true;
            }else if (beanDefinition.getSource() instanceof AnnotatedTypeMetadata){
                return AnnotatedTypeMetadata.class.cast(beanDefinition.getSource()).isAnnotated(annotionType.getName());
            }
            return false;
        });
    }

}
